/*
Copyright SecureKey Technologies Inc. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package dispatcher

import (
	cb "git.cloud.inspur.com/ichain/ichain-protos-go/common"
	pb "git.cloud.inspur.com/ichain/ichain-protos-go/inode"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/common/logging"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/ichain/events/api"
	clientdisp "git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/ichain/events/client/connector"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/ichain/events/deliverclient/connection"
	esdispatcher "git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/ichain/events/service/dispatcher"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/providers/context"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/providers/ichain"
	"github.com/pkg/errors"
)

var logger = logging.NewLogger("ichainsdk/ichain")

type Dispatcher struct {
	Connector *clientdisp.Connector // deliver 连接器，与inode相连
}

func New(context context.Client, chConfig ichain.ChainCfg, discoveryService ichain.DiscoveryService, connectionProvider api.ConnectionProvider) *Dispatcher {
	return &Dispatcher{
		Connector: clientdisp.New(context, chConfig, discoveryService, connectionProvider),
	}
}

func (ed *Dispatcher) Start() error {
	ed.registerHandlers()
	if err := ed.Connector.Start(); err != nil {
		return errors.WithMessage(err, "error starting deliver event dispatcher")
	}
	return nil
}

func (ed *Dispatcher) connection() api.Connection {
	return ed.Connector.Connection()
}

func (ed *Dispatcher) handleSeekEvent(e esdispatcher.Event) {
	evt := e.(*SeekEvent)

	if ed.Connector.Connection() == nil {
		logger.Warn("Unable to register chain since no connection was established.")
		return
	}

	if err := ed.connection().Send(evt.SeekInfo); err != nil {
		evt.ErrCh <- errors.Wrapf(err, "error sending seek info for chain [%s]", ed.Connector.ChainConfig().ID())
	} else {
		logger.Infof("success sending seek info for chain [%s]", ed.Connector.ChainConfig().ID())
		evt.ErrCh <- nil
	}
}

func (ed *Dispatcher) handleEvent(e esdispatcher.Event) {
	deliverEvent := e.(*connection.Event)
	evt := deliverEvent.Event.(*pb.DeliverResponse)
	switch response := evt.Type.(type) {
	case *pb.DeliverResponse_Status:
		ed.handleDeliverResponseStatus(response)
	case *pb.DeliverResponse_Block:
		ed.Connector.HandleBlock(response.Block, deliverEvent.SourceURL)
	case *pb.DeliverResponse_FilteredBlock:
		ed.Connector.HandleFilteredBlock(response.FilteredBlock, deliverEvent.SourceURL)
	default:
		logger.Errorf("handler not found for deliver response type %T", response)
	}
}

func (ed *Dispatcher) handleDeliverResponseStatus(evt *pb.DeliverResponse_Status) {
	logger.Debugf("Got deliver response status event: %#v", evt)

	if evt.Status == cb.Status_SUCCESS {
		return
	}

	logger.Warnf("Got deliver response status event: %#v. Disconnecting...", evt)

	errch := make(chan error, 1)
	ed.Connector.HandleDisconnectEvent(&clientdisp.DisconnectEvent{
		Errch: errch,
	})
	err := <-errch
	if err != nil {
		logger.Warnf("Error disconnecting: %s", err)
	}

	ed.Connector.HandleDisconnectedEvent(disconnectedEventFromStatus(evt.Status))
}

func (ed *Dispatcher) registerHandlers() {
	ed.Connector.RegisterHandler(&SeekEvent{}, ed.handleSeekEvent)
	ed.Connector.RegisterHandler(&connection.Event{}, ed.handleEvent)
}

func disconnectedEventFromStatus(status cb.Status) *clientdisp.DisconnectedEvent {
	err := errors.Errorf("got error status from deliver server: %s", status)

	if status == cb.Status_FORBIDDEN {
		return clientdisp.NewFatalDisconnectedEvent(err)
	}
	return clientdisp.NewDisconnectedEvent(err)
}

func (ed *Dispatcher) EventCh() (chan<- interface{}, error) {
	return ed.Connector.EventCh()
}

func (ed *Dispatcher) LatestBlockNum() uint64 {
	return ed.Connector.LatestBlockNum()
}
